%%%----------------------------------------------------------------------
%%% File    : client_simu.erl
%%% Author  : chen.kangmin
%%%----------------------------------------------------------------------

-module(client_simu).
-author('chen.kangmin').


-behaviour(gen_server).


-export([
    start/1,
    start/4,
    random2/1
        ]).


-export([init/1,
         code_change/3,
         handle_info/2,
         handle_call/3,
         handle_cast/2,
         terminate/2
        ]).

-export([utc_str/0]).


-include("../cus/cus_pb.hrl").


-record(state, {socket,
                uid = undefined,
                key,
                bound = undefined,
                data = <<>>,
                size = 0
               }).

-define(Flag, false).

stop() ->
    gen_server:cast(self(), stop).


message_ack(MsgList) ->
    gen_server:cast(self(), {message_ack, MsgList}).


send_msg(ConversationID) ->
    gen_server:cast(self(), {send_msg, ConversationID}).


%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Uid) ->
    Token = cus_util:to_list(client:get_user_token(Uid)),
    PushKey = cus_util:hex_str(crypto:rand_bytes(16)),
    Platform = case cus_util:random(2) of
                 1 -> "ios";
                 _ -> "android"
               end,
    start(Uid, Token, Platform, PushKey).


start(Uid, Token, Platform, PushKey) ->
    gen_server:start_link({local, list_to_atom("client" ++ Uid)},  ?MODULE, [Uid, Token, Platform, PushKey], []).


decode_protocal(ProtoBin, -1) ->    %%错误
    cus_pb:decode_error(ProtoBin);
decode_protocal(ProtoBin, 2) ->     %%登录返回
    cus_pb:decode_loginresp(ProtoBin);
decode_protocal(ProtoBin, 3) ->     %%消息
    cus_pb:decode_msg(ProtoBin);
decode_protocal(ProtoBin, 4) ->     %%消息ack
    cus_pb:decode_messageack(ProtoBin);
decode_protocal(_ProtoBin, _) ->
    throw("unknow protocol").

%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData}          |
%%          {ok, StateName, StateData, Timeout} |
%%          ignore                              |
%%          {stop, StopReason}
%%----------------------------------------------------------------------
init([Uid, Token, Platform, PushKey]) ->
%%     io:format("start user:~n",[]),
    Host = application:get_env(client, host, "127.0.0.1"),
    Port = application:get_env(client, port, 5222),

    {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, true}]),

  case ?Flag of
    true ->
      PublicKeyFile = application:get_env(client, key_file, "/data/ss/HUBIMServerCore/key/public.key"),
      [KeyInfo] = pem_to_der(PublicKeyFile),
      PublicKey = decode_private_key(KeyInfo, ""),

      Random = crypto:rand_bytes(16),
      Cipher = public_key:encrypt_public(Random, PublicKey),

      %%握手协议，同步接收
      %    Cipher1 = "uoO3iWszn0mJCCma/UL+hr/Zey7VYk0W7GjReaAyBX8eEY4uaFFTpn+5HMyMkYwrEr80ky0tE3WQz7+1pRNOLdKNftTr5k3nB3dNEX+6fH06lWwZHBUmzSufuORX4RlivnxyJ+vsHWJqnzKbCI+sat++5EszwvPumhpP6drDbbE=",
      %    Handshake = #handshake{secretkey = Cipher1},
      Handshake = #handshake{secretkey = binary_to_list(base64:encode(Cipher))},
      HandshakeBin = cus_pb:encode(Handshake),
%%     io:format("HandshakeBin Size : ~p~n", [size(HandshakeBin)]),
      gen_tcp:send(Socket, HandshakeBin),
      receive
        {tcp, Socket, Bin} ->
          _Record = cus_pb:decode_handshakeresp(Bin),
%%             io:format("HandshakeResp Record : ~p~n", [Record]),

          %%登录，同步接收
          Login = #login{uid = Uid, server = "xiaoquan.com", token = Token, platform = Platform, pushkey = PushKey},
          LoginPlainText = cus_pb:encode(Login),
          % LoginBin = crypto:block_encrypt(aes_cbc128, Random, <<"1111111111111111">>, pkcs5_padding(LoginPlainText, 16)),
          LoginBin = crypto:aes_cbc_128_encrypt(Random, <<"1111111111111111">>, pkcs5_padding(LoginPlainText, 16)),
          Bound = #bound{type = 1, size = size(LoginBin)},
          BoundBin = cus_pb:encode(Bound),
          gen_tcp:send(Socket, <<BoundBin/binary, LoginBin/binary>>),
          gen_tcp:controlling_process(Socket, self()),
          Rand = random2(240),
          erlang:send_after(Rand*1000, self(), {loop_msg}),
          {ok, #state{socket = Socket, key = Random, uid = Uid}};
        {tcp_closed, Socket} ->
          gen_tcp:close(Socket),
          {stop, normal}
      end;
    _ ->
      %%登录，同步接收
      Login = #login{uid = Uid, server = "xiaoquan.com", token = Token, platform = Platform, pushkey = PushKey},
      LoginPlainText = cus_pb:encode(Login),
      Bound = #bound{type = 1, size = size(LoginPlainText)},
      BoundBin = cus_pb:encode(Bound),
      gen_tcp:send(Socket, <<BoundBin/binary, LoginPlainText/binary>>),
      gen_tcp:controlling_process(Socket, self()),
      Rand = random2(120) + 60,
      erlang:send_after(Rand*1000, self(), {loop_msg}),
      {ok, #state{socket = Socket, key = <<>>, uid = Uid}}
  end.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({message_ack, MsgList}, State) ->
%%     io:format("message ack : ~p~n", [MsgList]),
    [begin
         Ack = #messageack{
             mid = Msg#msg.mid,
             sid = Msg#msg.mid,
             msgtime = Msg#msg.msgtime,
             extra = ""
                          },
%%          io:format("send msg ack : ~p~n", [Ack]),
         AckPlainText = cus_pb:encode(Ack),
        %AckBin = crypto:block_encrypt(aes_cbc128, State#state.key, <<"1111111111111111">>, pkcs5_padding(AckPlainText, 16)),
         AckBin = case ?Flag of
           true ->
             crypto:aes_cbc_128_encrypt(State#state.key, <<"1111111111111111">>, pkcs5_padding(AckPlainText, 16));
           _ ->
             AckPlainText
         end,
         Bound = #bound{type = 4, size = size(AckBin)},
         BoundBin = cus_pb:encode(Bound),
         gen_tcp:send(State#state.socket, <<BoundBin/binary, AckBin/binary>>)
     end || Msg <- MsgList],
    {noreply, State};

handle_cast({send_msg, ConversationID}, State) ->
    %%消息，异步接收
    Content = {obj, [{text, <<"this is test msg">>}, {fromUserName, <<"name">>}, {fromUserIcon, <<"icon">>}]},
    J = rfc4627:encode(Content),
    Msg = #msg{mid = utc_str(), conversationid = ConversationID, msgtype = "text", fromuser = State#state.uid, members = "", content = J,
      msgstatus = "0",
      msgtime = utc_str()},
    MsgPlainText = cus_pb:encode(Msg),
  % MsgBin = crypto:block_encrypt(State#state.key, <<"1111111111111111">>, pkcs5_padding(MsgPlainText, 16))
    MsgBin = case ?Flag of
      true ->
        crypto:aes_cbc_128_encrypt(State#state.key, <<"1111111111111111">>, pkcs5_padding(MsgPlainText, 16));
      _ ->
        MsgPlainText
    end,
    Bound = #bound{type = 3, size = size(MsgBin)},
    BoundBin = cus_pb:encode(Bound),
    gen_tcp:send(State#state.socket, <<BoundBin/binary, MsgBin/binary>>),
    {noreply, State};

handle_cast(stop, State) ->
    {stop, normal, State};

handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({tcp, _TCPSocket, Data}, State) ->
    OldBuff = State#state.data,
    OldSize = State#state.size,
    State1 = State#state{data = <<OldBuff/binary, Data/binary>>, size = OldSize + size(Data)},
    try
        State2 = parse_data(State1),
        {noreply, State2}
    catch
        Type:Err ->
            io:format("PARSE DATA ERROR, TYPE : ~p, ERROR : ~p", [Type, Err]),
            {stop, normal, State}
    end;
    
handle_info({tcp_closed, _TCPSocket}, State) ->
    {stop, normal, State};

handle_info({tcp_error, _TCPSocket, Reason}, State) ->
    case Reason of
    	timeout ->
    	    {noreply, State};
    	_ ->
    	    {stop, normal, State}
    end;
handle_info({loop_msg}, #state{uid=UID}=State) ->
  Rand = random2(240),
%%   io:format("user rand 2: ~p~n", [{UID, Rand}]),
  erlang:send_after(Rand*1000, self(), {loop_msg}),
  Sql = io_lib:format("select BrandSpaceID from BrandSpaceMemberInfo where BrandSpaceMemberUserID = ~p", [UID]),
%%   io:format("loop msg sql : ~w~n", [Sql]),
  case catch cus_mysql:get_all(Sql) of
    List when is_list(List) ->
      N = length(List),
      if N > 0 ->
          [CID] = lists:nth(random:uniform(N), List),
%%           io:format(" cid :~p~n", ["Q_" ++ integer_to_list(CID)]),
          gen_server:cast(self(), {send_msg, "Q_" ++ integer_to_list(CID)});
        true ->
          next
      end;
    _ ->
      next
  end,
  {noreply, State};

handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
catch gen_tcp:close(State#state.socket).

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


parse_data(State) when State#state.bound =:= undefined andalso State#state.size >= 10 ->
    Data = State#state.data,
    Size = State#state.size - 10,
    <<BoundBin:10/binary, Buff:Size/binary>> = Data,
    Bound = cus_pb:decode_bound(BoundBin),
    if
        Bound#bound.type =:= 0 ->   %%心跳协议
            parse_data(State#state{data = Buff, size = Size});
        true ->
            if
                Size >= Bound#bound.size ->
                    Size1 = Bound#bound.size,
                    Size2 = Size - Size1,
                    <<BusinessBin:Size1/binary, Buff1:Size2/binary>> = Buff,
                    ProtoBin =
                    case ?Flag of
                      true ->
                        pkcs5_unpadding(crypto:aes_cbc_128_decrypt(State#state.key, <<"1111111111111111">>, BusinessBin), 16);
                      _ ->
                        BusinessBin
                    end,
                  % ProtoBin = pkcs5_unpadding(crypto:block_decrypt(aes_cbc128, State#state.key, <<"1111111111111111">>, BusinessBin), 16)),
                    Record = decode_protocal(ProtoBin, Bound#bound.type),
%%                     io:format("RESP RECORD 1 : ~p~n", [Record]),
                    do_record(Record),
                    parse_data(State#state{data = Buff1, size = Size2, bound = undefined});
                true ->
                    State#state{data = Buff, size = Size, bound = Bound}
            end
    end;
parse_data(State) when State#state.bound =:= undefined ->
    State;
parse_data(State) ->
    Bound = State#state.bound,
    if
        State#state.size >= Bound#bound.size ->
            Data = State#state.data,
            Size1 = Bound#bound.size,
            Size = State#state.size - Size1,
            <<BusinessBin:Size1/binary, Buff:Size/binary>> = Data,
          % ProtoBin = pkcs5_unpadding(crypto:block_decrypt(aes_cbc128, State#state.key, <<"1111111111111111">>, BusinessBin), 16)),
            ProtoBin =
            case ?Flag of
              true ->
                pkcs5_unpadding(crypto:aes_cbc_128_decrypt(State#state.key, <<"1111111111111111">>, BusinessBin), 16);
              _ ->
                BusinessBin
            end,
            Record = decode_protocal(ProtoBin, Bound#bound.type),
%%             io:format("RESP RECORD 2: ~p~n", [Record]),
            do_record(Record),
            parse_data(State#state{data = Buff, size = Size, bound = undefined});
        true ->
            State
    end.


do_record(Record) when is_record(Record, loginresp) ->
  skip;
do_record(Record) when is_record(Record, msg) ->
%%     skip;
    message_ack([Record]);
do_record(Record) when is_record(Record, messageack) ->
    %%做存储消息，更新本地msgid的操作
    skip;
do_record(_Record) ->
    skip.


decode_private_key({_, _, not_encrypted} = KeyInfo, _) ->
    public_key:pem_entry_decode(KeyInfo);
decode_private_key(KeyInfo, Password) ->
    public_key:pem_entry_decode(KeyInfo, Password).

pem_to_der(File) ->
    {ok, Content} = file:read_file(File),
    public_key:pem_decode(Content).


utc_str() ->
    {MegaSecs, Secs, MicroSecs} = now(),
    integer_to_list(MegaSecs * 1000000 + Secs + MicroSecs).


pkcs5_padding(PlainText, BlockSize) when is_binary(PlainText) ->
    Rem = size(PlainText) rem BlockSize,
    Padding = lists:duplicate(BlockSize - Rem, BlockSize - Rem),
    Binary = list_to_binary(Padding),
    <<PlainText/binary, Binary/binary>>;
pkcs5_padding(PlainText, BlockSize) when is_list(PlainText) ->
    Rem = length(PlainText) rem BlockSize,
    Padding = lists:duplicate(BlockSize - Rem, BlockSize - Rem),
    PlainText ++ Padding.

pkcs5_unpadding(PlainText, _BlockSize) when is_binary(PlainText) ->
    TextSize = size(PlainText) ,
    Size = TextSize -1,
    <<_:Size/binary, PaddingSize>> = PlainText,
    PreSize = TextSize - PaddingSize,
    <<Pre:PreSize/binary, _/binary>> = PlainText,
    Pre;
pkcs5_unpadding(PlainText, _BlockSize) when is_list(PlainText) ->
    PaddingSize = lists:last(PlainText),
    {Pre, _} = lists:split(length(PlainText) - PaddingSize, PlainText),
    Pre.


%% 大包持续接受数据
recv_tcp(Bin, BoundSize) ->
  case size(Bin) < BoundSize of
    true ->
      Bin2=
        receive
          {tcp, _Socket, Bin1} ->
            <<Bin/binary, Bin1/binary>>;
          _Msg ->
            io:format("receive another part msg error:~p~n", [_Msg]),
            Bin
        end,
      recv_tcp(Bin2, BoundSize);
    false ->
      Bin
  end.


random2(N) ->
  random:seed(now()),
  random:uniform(N).